﻿/***********************************
 * Simple CSharp WebSocket Server( Protocal Draft-10 )
 * 
 * Author : Kestin
 * 
 * Support Browser
 *    Chrome 14+
 *    Firefox 7+(not full test)
 ***********************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using SharpWebSocket.Protocol;
using System.Web.Routing;

namespace SharpWebSocket
{
    public class WebSocketRuntime
    {
        #region Config Value
        public const int RECEIVE_DATA_SIZE = 1024;
        #endregion

        #region Event
        public event WebSocketConnected OnNewConnection;

        public event DataReceived OnDataReceived;

        public event SocketClosed OnSocketClosed;

        internal void RaiseNewConnectionEvent( Socket newSocket, string url )
        {
            if (OnNewConnection != null)
            {
                var e = new WebSocketConnectedEventArgs( newSocket, new Uri( url ) );
                OnNewConnection( this, e );
            }
        }

        internal void RaiseDataReceivedEvent( Socket clientSocket, string receivedData )
        {
            if (OnDataReceived != null)
            {
                var e = new DataReceivedEventArgs( receivedData );
                OnDataReceived( clientSocket, e );
            }
        }

        internal void RaiseSocketClosedEvent( Socket socket, SocketError socketError = SocketError.Success )
        {
            lock (_activePool)
            {
                _activePool.Remove( socket );
            }

            if (OnSocketClosed != null)
            {
                var e = new SocketClosedEventArgs( socket, socketError );
                OnSocketClosed( this, e );
            }
        }
        #endregion

        #region Construct & Internal Member
        private Socket socketListener = null;
        internal Dictionary<Socket, WebSocketHandler> _activePool = null;

        public WebSocketRuntime( IPEndPoint localEndpoint )
        {
            _activePool = new Dictionary<Socket, WebSocketHandler>();
            socketListener = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );
            socketListener.Bind( localEndpoint );
        }
        #endregion

        #region WebSocketServer Controller
        public void Start( int backlog = 100 )
        {
            socketListener.Listen( backlog );
            Console.WriteLine( "WebSocket Host Server Start..." );
            Console.WriteLine( "Listening " + socketListener.LocalEndPoint.ToString() );

            AsyncWaitNewConnection();
        }

        public void Stop()
        {
            socketListener.Close();
        }
        #endregion

        #region WebSocketServer Core
        private void AsyncWaitNewConnection()
        {
            socketListener.BeginAccept( new AsyncCallback( ( IAsyncResult result ) =>
            {
                Socket listener = (Socket)result.AsyncState;
                Socket newSocket = listener.EndAccept( result );

                AsyncWaitNewConnection();

                HandShake( newSocket );

                if (!newSocket.Connected)
                {
                    return;
                }

                // test send
                newSocket.Send( Draft10.WrapResponse( "abcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefg1234567" ) );

                lock (_activePool)
                {
                    var handler = new WebSocketHandler();
                    handler.Init( new WebSocketContext( this ), newSocket );
                    _activePool.Add( newSocket, handler );
                }

                RaiseNewConnectionEvent( newSocket, "" );
                
                AsyncWaitNewMessage( newSocket );
            } ), socketListener );
        }

        private void AsyncWaitNewMessage( Socket socket )
        {
            Message msgDummy = new Message( socket );

            socket.BeginReceive( msgDummy.Buffer, 0, msgDummy.Buffer.Length, SocketFlags.None,
                new AsyncCallback( ( IAsyncResult result ) =>
                {
                    Message msg = (Message)result.AsyncState;
                    Socket clientSocket = msg.Socket;
                    SocketError error = SocketError.Success;
                    int readcount = clientSocket.EndReceive( result, out error );
                    if (readcount == 0 || error != SocketError.Success)
                    {
                        // no data read or has any error
                        clientSocket.Close();
                        RaiseSocketClosedEvent( clientSocket, error );
                    }
                    else
                    {
                        string receivedData = Draft10.WrapRequest( msg.Buffer, readcount );
                        // Console.WriteLine( receivedData );

                        try
                        {
                            _activePool[clientSocket].OnDataReceived( new DataReceivedEventArgs( receivedData ) );

                            // RaiseDataReceivedEvent( clientSocket, receivedData );
                            AsyncWaitNewMessage( clientSocket );
                        }
                        catch( WebSocketCloseException )
                        {
                            lock (_activePool)
                            {
                                _activePool.Remove( clientSocket );
                            }
                        }
                    }
                } ), msgDummy );
        }

        private void HandShake( Socket clientSocket )
        {
            var request = Draft10.BuildHandShakeRequest( clientSocket );
            var response = Draft10.BuildHandShakeResponse( request );
            SocketError err = response.CompleteHandShake( clientSocket );

            if (err == SocketError.Success)
            {
                Console.WriteLine( "Client Socket {0} Handshake Completed", clientSocket.RemoteEndPoint.ToString() );
            }
            else
            {
                Console.WriteLine( "Client Socket {0} Handshake Error, Then Closed", clientSocket.RemoteEndPoint.ToString() );
                clientSocket.Shutdown( SocketShutdown.Both );
                clientSocket.Close();
            }
        }
        #endregion

        public RouteCollection UrlRoute
        {
            get;
            set;
        }
    }
}
